package com.bingxue.edu.management.education.service;

import cn.hutool.core.convert.Convert;
import com.bingxue.edu.common.config.EducationProperties;
import com.bingxue.edu.common.enums.ClassStatusEnum;
import com.bingxue.edu.common.enums.JoinTypeEnum;
import com.bingxue.edu.common.enums.LessonStatusEnum;
import com.bingxue.edu.common.enums.OperateSourceEnum;
import com.bingxue.edu.common.enums.StudentClassEnum;
import com.bingxue.edu.framework.common.model.BaseEntity;
import com.bingxue.edu.framework.common.util.ValidationUtils;
import com.bingxue.edu.framework.mybatis.base.BaseService;
import com.bingxue.edu.management.education.mapper.ClassInfoMapper;
import com.bingxue.edu.management.education.mapper.LessonMapper;
import com.bingxue.edu.management.education.model.entity.ArrangeLesson;
import com.bingxue.edu.management.education.model.entity.ClassInfo;
import com.bingxue.edu.management.education.model.entity.Classroom;
import com.bingxue.edu.management.education.model.entity.Lesson;
import com.bingxue.edu.management.education.model.entity.LessonStudent;
import com.bingxue.edu.management.education.model.entity.TimeTemplate;
import com.bingxue.edu.management.education.model.query.ClassLessonQuery;
import com.bingxue.edu.management.education.model.req.ClassScheduleReq;
import com.bingxue.edu.management.education.model.req.ClassroomScheduleReq;
import com.bingxue.edu.management.education.model.req.LessonReq;
import com.bingxue.edu.management.education.model.req.LessonSignInReq;
import com.bingxue.edu.management.education.model.resp.ArrangeTemplateDetailResp;
import com.bingxue.edu.management.education.model.resp.ClassLessonResp;
import com.bingxue.edu.management.education.model.resp.ClassPreScheduleResp;
import com.bingxue.edu.management.education.model.resp.ClassroomScheduleInfoResp;
import com.bingxue.edu.management.education.model.resp.ConflictLessonResp;
import com.bingxue.edu.management.education.model.resp.StudentScheduleResp;
import com.bingxue.edu.management.report.model.resp.LessonSignInStat;
import com.bingxue.edu.management.shop.model.entity.OrderItem;
import com.bingxue.edu.management.system.model.entity.Employee;
import com.bingxue.edu.management.system.service.EmployeeService;
import com.mybatisflex.core.query.CaseSearchQueryColumn;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.update.UpdateChain;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.bingxue.edu.management.education.model.entity.table.ClassInfoTableDef.CLASS_INFO;
import static com.bingxue.edu.management.education.model.entity.table.ClassroomTableDef.CLASSROOM;
import static com.bingxue.edu.management.education.model.entity.table.LessonStudentTableDef.LESSON_STUDENT;
import static com.bingxue.edu.management.education.model.entity.table.LessonTableDef.LESSON;
import static com.bingxue.edu.management.system.model.entity.table.EmployeeTableDef.EMPLOYEE;
import static com.bingxue.edu.management.system.model.entity.table.OrgTableDef.ORG;

/**
 * 班级课次实现类
 *
 * @author zq
 * @since 2024/08/24
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LessonService extends BaseService<LessonMapper, Lesson> {

    private final LessonStudentService lessonStudentService;
    private final EmployeeService employeeService;
    private final ClassroomService classroomService;
    private final ArrangeTemplateService arrangeTemplateService;
    private final TimeTemplateService timeTemplateService;
    private final ClassInfoMapper classInfoMapper;
    private final EducationProperties educationProperties;

    public static CaseSearchQueryColumn weekNameColumn() {
        return QueryMethods.case_(QueryMethods.dayOfWeek(LESSON.LESSON_DATE))
                .when(1).then("星期日")
                .when(2).then("星期一")
                .when(3).then("星期二")
                .when(4).then("星期三")
                .when(5).then("星期四")
                .when(6).then("星期五")
                .when(7).then("星期六")
                .end();
    }

    public List<ClassLessonResp> getClassLessonList(ClassLessonQuery query) {
        ValidationUtils.throwIfEmpty(query.getClassId(), "班级id不能为空");
        List<ClassLessonResp> resp = this.queryChain()
                .select(LESSON.ALL_COLUMNS, weekNameColumn().as(ClassLessonResp::getWeekName))
                .from(LESSON)
                .where(LESSON.CLASS_ID.eq(query.getClassId()))
                .orderBy(LESSON.LESSON_INDEX.asc())
                .withRelations()
                .listAs(ClassLessonResp.class);
        Map<String, ClassLessonResp> scheduleLessonMap = resp.stream().collect(Collectors.toMap(BaseEntity::getId, item -> item));
        List<LessonSignInStat> lessonSignInStatList = lessonStudentService.getLessonSignInStat(scheduleLessonMap.keySet());
        for (LessonSignInStat lessonSignInStat : lessonSignInStatList) {
            ClassLessonResp item = scheduleLessonMap.get(lessonSignInStat.getLessonId());
            if (item != null) {
                item.setTotalCount(lessonSignInStat.getTotalCount());
                item.setSignInCount(lessonSignInStat.getSignInCount());
            }
        }
        return resp;
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteLessonByClassId(String classId) {
        this.getMapper().deleteByCondition(LESSON.CLASS_ID.eq(classId));
    }

    /**
     * 根据课次顺序号查询班级课次
     *
     * @param classId   班级id
     * @param indexList 课次顺序
     * @return 课次列表
     */
    public List<Lesson> getLessonByIndex(String classId, List<Integer> indexList) {
        return this.queryChain()
                .where(LESSON.CLASS_ID.eq(classId))
                .and(LESSON.LESSON_INDEX.in(indexList))
                .list();
    }

    /**
     * 根据课次顺序号查询班级课次
     *
     * @param classId   班级id
     * @param index 课次顺序
     * @return 课次列表
     */
    public Lesson getLessonByIndex(String classId, Integer index) {
        return this.queryChain()
                .where(LESSON.CLASS_ID.eq(classId))
                .and(LESSON.LESSON_INDEX.eq(index))
                .one();
    }

    /**
     * 根据班级id获取课次信息
     * @param classId 班级id
     */
    public List<Lesson> getLessonByClassId(String classId) {
        return queryChain()
                .where(LESSON.CLASS_ID.eq(classId))
                .orderBy(LESSON.LESSON_INDEX.asc())
                .list();
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateClassLesson(LessonReq req, String id) {
        Lesson newLesson = this.copyById(req, id);
        newLesson.setStartTime(LocalDateTime.of(req.getLessonDate(), req.getStartTime()));
        newLesson.setEndTime(LocalDateTime.of(req.getLessonDate(), req.getEndTime()));
        newLesson.setLessonIndex(0);
        this.updateById(newLesson);
        //查询所有的课次
        List<Lesson> lessonList = this.queryChain()
                .where(LESSON.CLASS_ID.eq(newLesson.getClassId()))
                .orderBy(LESSON.LESSON_DATE.asc())
                .list();
        // 重排序
        int index = 1;
            for (Lesson lesson : lessonList) {
            lesson.setLessonIndex(index);
            index += 1;
        }
        this.updateBatch(lessonList);
        log.info("课次更新成功, req: {}", req);
    }

    public List<ClassPreScheduleResp> schedule(ClassScheduleReq req) {
        //校验入参
        Employee employee = employeeService.getById(req.getLeadTeacherId());
        ValidationUtils.throwIfNull(employee,"教师不存在");
        Classroom classroom = classroomService.getById(req.getClassroomId());
        ValidationUtils.throwIfNull(classroom, "教室不存在");
        // 查询期段信息
        ArrangeTemplateDetailResp arrangeTemplateDetail = arrangeTemplateService.getDetailById(req.getArrangeTemplateId());
        ValidationUtils.throwIfNull(arrangeTemplateDetail,"期段不存在");
        // 查询时间段信息
        TimeTemplate timeTemplate = timeTemplateService.getById(req.getTimeTemplateId());
        ValidationUtils.throwIfNull(timeTemplate,"时间段不存在");

        return arrangeTemplateDetail.getArrangeLessonList().stream().map(arrangeLesson -> {
            ClassPreScheduleResp classPreScheduleResp = new ClassPreScheduleResp();

            LocalDateTime startTime = LocalDateTime.of(arrangeLesson.getLessonDate(), timeTemplate.getStartTime());
            LocalDateTime endTime = LocalDateTime.of(arrangeLesson.getLessonDate(), timeTemplate.getEndTime());

            classPreScheduleResp.setLessonDate(arrangeLesson.getLessonDate());
            classPreScheduleResp.setLessonStartTime(startTime);
            classPreScheduleResp.setLessonEndTime(endTime);
            classPreScheduleResp.setLessonIndex(arrangeLesson.getLessonIndex());

            QueryWrapper queryWrapper = QueryWrapper.create()
                    .select(
                            EMPLOYEE.NAME.as(ConflictLessonResp::getTeacherName),
                            CLASSROOM.NAME.as(ConflictLessonResp::getClassroomName),
                            CLASS_INFO.NAME.as(ConflictLessonResp::getClassName),
                            LESSON.ALL_COLUMNS
                    )
                    .from(LESSON)
                    .leftJoin(CLASS_INFO).on(LESSON.CLASS_ID.eq(CLASS_INFO.ID))
                    .leftJoin(CLASSROOM).on(LESSON.CLASSROOM_ID.eq(CLASSROOM.ID))
                    .leftJoin(EMPLOYEE).on(LESSON.LEAD_TEACHER_ID.eq(EMPLOYEE.ID))
                    .where(CLASS_INFO.CLASS_STATUS.notIn(ClassStatusEnum.CLOSED))
                    .and(LESSON.START_TIME.ge(startTime).and(LESSON.START_TIME.le(startTime))
                        .or(LESSON.END_TIME.ge(endTime).and(LESSON.END_TIME.le(endTime)))
                    )
                    .and(LESSON.CLASSROOM_ID.eq(req.getClassroomId())
                        .or(LESSON.LEAD_TEACHER_ID.eq(req.getLeadTeacherId()))
                    ).limit(1);
            ConflictLessonResp conflictLessonResp = this.getOneAs(queryWrapper, ConflictLessonResp.class);
            classPreScheduleResp.setConflictInfo(conflictLessonResp);
            return classPreScheduleResp;
        }).toList();
    }

    // TODO 移动位置 优化
    public List<StudentScheduleResp> getScheduleByStudentId(String studentId) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .select(
                        LESSON.LESSON_DATE,
                        LESSON.START_TIME,
                        LESSON.END_TIME,
                        CLASS_INFO.ID.as(StudentScheduleResp::getClassId),
                        CLASS_INFO.NAME.as(StudentScheduleResp::getClassName),
                        LESSON.LESSON_STATUS
                )
                .from(LESSON)
                    .leftJoin(LESSON_STUDENT).on(LESSON.ID.eq(LESSON_STUDENT.LESSON_ID))
                    .leftJoin(CLASS_INFO).on(LESSON_STUDENT.LESSON_CLASS_ID.eq(CLASS_INFO.ID))
                .where(LESSON_STUDENT.STUDENT_ID.eq(studentId))
                    .and(LESSON_STUDENT.STUDENT_CLASS_STATUS.eq(StudentClassEnum.NORMAL))
                .orderBy(LESSON.LESSON_INDEX.asc());
        return this.getMapper().selectListByQueryAs(queryWrapper, StudentScheduleResp.class);
    }

    public void signIn(LessonSignInReq req) {
        checkSignIn(req);
        lessonStudentService.updateChain()
                .set(LESSON_STUDENT.SIGN_IN_TIME, LocalDateTime.now())
                .set(LESSON_STUDENT.SIGN_IN_STATUS, true)
                .set(LESSON_STUDENT.OPERATE_SOURCE, req.getOperateSource())
                .where(LESSON_STUDENT.STUDENT_ID.in(req.getStudentIdList()))
                    .and(LESSON_STUDENT.LESSON_ID.eq(req.getLessonId()))
                    .and(LESSON_STUDENT.SIGN_IN_STATUS.eq(false))
                .update();
        log.info("点名成功, {}", req);
    }

    private void checkSignIn(LessonSignInReq req) {
        //获取课次信息
        Lesson classLesson = this.getById(req.getLessonId());
        ValidationUtils.throwIfNull(classLesson, "课次不存在");
        if (req.getOperateSource() == OperateSourceEnum.BACKEND) {
            return;
        }
        if (!educationProperties.getSignIn().getTimeLimited()) {
            return;
        }
        // 课程开始前时间限制
        Integer timeLimitHourBefore = educationProperties.getSignIn().getTimeLimitHourBefore();
        LocalDateTime hourBefore = classLesson.getStartTime().minusHours(timeLimitHourBefore);
        ValidationUtils.throwIf(LocalDateTime.now().isBefore(hourBefore), "未到允许操作时间");

        // 课程结束后时间限制
        Integer timeLimitNextMonthDayAfter = educationProperties.getSignIn().getTimeLimitNextMonthDayAfter();
        LocalDate dayOfNextMonth = classLesson.getLessonDate().plusMonths(1).withDayOfMonth(timeLimitNextMonthDayAfter);
        LocalDateTime dayStartOfNextMonth = dayOfNextMonth.atStartOfDay();
        ValidationUtils.throwIf(LocalDateTime.now().isAfter(dayStartOfNextMonth), "超过允许操作时间");
    }

    // TODO 优化
    public List<ClassroomScheduleInfoResp> getLessonInfoByLocalDate(ClassroomScheduleReq req) {
        QueryWrapper queryWrapper = buildLessonQueryWrapper();
        queryWrapper.where(LESSON.CLASSROOM_ID.eq(req.getClassroomId()))
                .and(LESSON.CAMPUS_ID.eq(req.getCampusId()))
                .and(LESSON.LESSON_DATE.ge(req.getStartTime()))
                .and(LESSON.LESSON_DATE.le(req.getEndTime()))
                .and(LESSON.LESSON_STATUS.ne(LessonStatusEnum.COMPLETED))
                .and(LESSON.LEAD_TEACHER_ID.eq(req.getLeadTeacherId()))
                .and(LESSON.ASSIST_TEACHER_ID.eq(req.getAssistTeacherId()));
        return this.getMapper().selectListByQueryAs(queryWrapper,ClassroomScheduleInfoResp.class);
    }

    private QueryWrapper buildLessonQueryWrapper() {
        return QueryWrapper.create()
                .select(
                        LESSON.DEFAULT_COLUMNS,
                        CLASS_INFO.NAME.as(ClassroomScheduleInfoResp::getClassName),
                        EMPLOYEE.NAME.as(ClassroomScheduleInfoResp::getLeadTeacherName),
                        CLASSROOM.NAME.as(ClassroomScheduleInfoResp::getClassroomName),
                        ORG.NAME.as(ClassroomScheduleInfoResp::getCampusName)
                )
                .from(LESSON)
                .leftJoin(CLASS_INFO).on(LESSON.CLASS_ID.eq(CLASS_INFO.ID))
                .leftJoin(EMPLOYEE).on(LESSON.LEAD_TEACHER_ID.eq(EMPLOYEE.ID))
                .leftJoin(CLASSROOM).on(LESSON.CLASSROOM_ID.eq(CLASSROOM.ID))
                .leftJoin(ORG).on(LESSON.CAMPUS_ID.eq(ORG.ID));
    }

    public void unSignIn(LessonSignInReq req) {
        checkSignIn(req);
        lessonStudentService.updateChain()
                .set(LESSON_STUDENT.SIGN_IN_TIME, null)
                .set(LESSON_STUDENT.SIGN_IN_STATUS, false)
                .set(LESSON_STUDENT.OPERATE_SOURCE, null)
                .where(LESSON_STUDENT.STUDENT_ID.in(req.getStudentIdList()))
                    .and(LESSON_STUDENT.LESSON_ID.eq(req.getLessonId()))
                    .and(LESSON_STUDENT.SIGN_IN_STATUS.eq(true))
                .update();
        log.info("撤销点名成功, {}", req);
    }

    /**
     *  新增课次学员信息
     * @param orderItem 订单项信息
     * @param studentId 学生id
     * @param classInfo 班级信息
     * @param id 报名id
     * @param enroll 加入状态：报名
     */
    @Transactional(rollbackFor = Exception.class)
    public void addLessonToStudent(OrderItem orderItem, String studentId, ClassInfo classInfo, String id, JoinTypeEnum enroll) {
        // 课次顺序
        List<Integer> lessonIndexes = Convert.toList(Integer.class, orderItem.getLessonIndexes());
        List<Lesson> lessonList = this.getLessonByIndex(classInfo.getId(), lessonIndexes);
        // 转换
        List<LessonStudent> lessonStudents = lessonList.stream().map(lesson -> {
            LessonStudent lessonStudent = lessonStudentService.initLessonStudent(studentId, classInfo.getId(), id, enroll);
            // 课次id
            lessonStudent.setLessonId(lesson.getId());
            // 课时：一次课次的课时=1*班级对应的课时业绩系数
            lessonStudent.setLessonHour(1 * classInfo.getLessonHourFactor());
            // 课消
            lessonStudent.setLessonPrice(orderItem.getActualUnitPrice());
            // 订单id
            lessonStudent.setOrderItemId(orderItem.getId());
            return lessonStudent;
        }).toList();
        lessonStudentService.saveBatch(lessonStudents);
    }

    public void createLessonBatch(ClassInfo classInfo, List<ArrangeLesson> arrangeLessonList, TimeTemplate timeTemplate) {
        List<Lesson> classLessonList = arrangeLessonList.stream()
                .sorted(Comparator.comparing(ArrangeLesson::getLessonIndex))
                .map(lessonTemplate -> {
                    Lesson lesson = new Lesson();
                    lesson.setLessonStatus(LessonStatusEnum.WAITING);
                    return updateNewLesson(lesson, classInfo, lessonTemplate, timeTemplate);
                }).toList();
        this.saveBatch(classLessonList);
        log.info("创建课次成功");
    }

    /**
     * 班级重新排课
     * @param classScheduleReq  班级排课请求
     */
    @Transactional(rollbackFor = Exception.class)
    public void reschedule(ClassScheduleReq classScheduleReq, String classId) {
        ClassInfo classInfo = classInfoMapper.selectOneById(classId);
        ValidationUtils.throwIfNull(classInfo, "班级不存在");

        ArrangeTemplateDetailResp arrangeTemplateDetail = arrangeTemplateService.getDetailById(classScheduleReq.getArrangeTemplateId());
        ValidationUtils.throwIfNull(arrangeTemplateDetail, "期段不存在");

        TimeTemplate newTimeTemplate = timeTemplateService.getById(classScheduleReq.getTimeTemplateId());
        ValidationUtils.throwIfNull(newTimeTemplate, "时间段不存在");

        //查询新排课模板信息
        List<ArrangeLesson> newArrangeLessonList = arrangeTemplateDetail.getArrangeLessonList();
        //查询该班级已排课信息
        List<Lesson> lessons = this.getLessonByClassId(classId);

        //校验重新排课已选择期段的课次是否和原排课的课次一致
        ValidationUtils.throwIf(!(lessons.size() == newArrangeLessonList.size()),"新排课课次数量必须和原课次相同，原课次数: {}，新课次数：{}", newArrangeLessonList.size(), lessons.size());

        //查询班级信息,新时间段模板信息
        classInfo.setCampusId(classScheduleReq.getCampusId());
        classInfo.setClassroomId(classScheduleReq.getClassroomId());
        classInfo.setLeadTeacherId(classScheduleReq.getLeadTeacherId());
        classInfo.setArrangeTemplateId(classScheduleReq.getArrangeTemplateId());
        classInfo.setTimeTemplateId(classScheduleReq.getTimeTemplateId());
        classInfoMapper.update(classInfo);
        //更新课次信息
        this.updateLessonBatch(classInfo, lessons, newArrangeLessonList, newTimeTemplate);

        log.info("重新排课成功: {}", classInfo);
    }

    /**
     * 根据班级信息,课次模板信息,上课时间段模板信息更新lesson信息
     * @param lesson 课次信息
     * @param classInfo 班级信息
     * @param arrangeLesson  课次模板信息
     * @param timeTemplate  时间段模板信息
     * @return 更新后的课次lesson信息
     */
    private Lesson updateNewLesson(Lesson lesson, ClassInfo classInfo, ArrangeLesson arrangeLesson, TimeTemplate timeTemplate) {
        lesson.setClassId(classInfo.getId());
        lesson.setCampusId(classInfo.getCampusId());
        lesson.setClassroomId(classInfo.getClassroomId());
        lesson.setLeadTeacherId(classInfo.getLeadTeacherId());
        lesson.setAssistTeacherId(classInfo.getAssistTeacherId());
        lesson.setLessonIndex(arrangeLesson.getLessonIndex());
        lesson.setTitle("第" + arrangeLesson.getLessonIndex() + "次课");
        lesson.setLessonDate(arrangeLesson.getLessonDate());
        //开始时间/结束时间 = 课次模板上课时间 + 上课时间模板时间
        LocalDateTime startTime = LocalDateTime.of(arrangeLesson.getLessonDate(), timeTemplate.getStartTime());
        LocalDateTime endTime = LocalDateTime.of(arrangeLesson.getLessonDate(), timeTemplate.getEndTime());
        lesson.setStartTime(startTime);
        lesson.setEndTime(endTime);
        lesson.setArrangeLessonId(arrangeLesson.getId());
        return lesson;
    }

    /**
     * 根据已排课信息和新排课模板信息修改原排课信息
     * @param classInfo 班级信息
     * @param lessons 原课次信息列表
     * @param newArrangeLessonList 新排课模板信息列表
     * @param newTimeTemplate  新时间模板信息列表
     */
    private void updateLessonBatch(ClassInfo classInfo, List<Lesson> lessons, List<ArrangeLesson> newArrangeLessonList, TimeTemplate newTimeTemplate) {
        List<Lesson> newLessonList  = new ArrayList<>();

        //更新单个lesson
        for (Lesson lesson : lessons) {
            ArrangeLesson newArrangeLesson = newArrangeLessonList.stream()
                    .filter(arrangeLesson -> arrangeLesson.getLessonIndex() != null && arrangeLesson.getLessonIndex().equals(lesson.getLessonIndex()))
                    .findFirst()
                    .orElseThrow();
            Lesson newLesson = updateNewLesson(lesson, classInfo, newArrangeLesson, newTimeTemplate);
            newLessonList.add(newLesson);
        }

        this.updateBatch(newLessonList);
    }

    @Transactional(rollbackFor = Exception.class)
    public void stopLessonByClassId(String classId) {
        this.updateChain()
                .where(LESSON.CLASS_ID.eq(classId))
                .set(LESSON.LESSON_STATUS, LessonStatusEnum.STOPPED)
                .update();
        log.info("停课成功：{}", classId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void completeLesson(Lesson lesson) {
        ValidationUtils.throwIfNull(lesson, "课次不存在");
        ValidationUtils.throwIfEqual(lesson.getLessonStatus(), LessonStatusEnum.STOPPED, "课次已停课");
        if (lesson.getLessonStatus() == LessonStatusEnum.COMPLETED) {
            return;
        }
        lesson.setLessonStatus(LessonStatusEnum.COMPLETED);
        boolean updated = this.updateById(lesson);
        if (updated) {
            UpdateChain.of(ClassInfo.class)
                       .set(CLASS_INFO.COMPLETED_LESSON_COUNT, CLASS_INFO.COMPLETED_LESSON_COUNT.add(1))
                       .where(CLASS_INFO.ID.eq(lesson.getClassId()))
                        .update();
        }
        log.warn("完成课次成功：{}", lesson.getId());
    }
}
